/*
 * FreeRTOS Kernel V11.1.0
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/*-----------------------------------------------------------
* Implementation of functions defined in portable.h for the RISC-V port.
*----------------------------------------------------------*/

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "portmacro.h"

/* Standard includes. */
#include "string.h"

#ifdef configCLINT_BASE_ADDRESS
    #warning "The configCLINT_BASE_ADDRESS constant has been deprecated. configMTIME_BASE_ADDRESS and configMTIMECMP_BASE_ADDRESS are currently being derived from the (possibly 0) configCLINT_BASE_ADDRESS setting.  Please update to define configMTIME_BASE_ADDRESS and configMTIMECMP_BASE_ADDRESS directly in place of configCLINT_BASE_ADDRESS. See www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html"
#endif

#ifndef configMTIME_BASE_ADDRESS
    #warning "configMTIME_BASE_ADDRESS must be defined in FreeRTOSConfig.h. If the target chip includes a memory-mapped mtime register then set configMTIME_BASE_ADDRESS to the mapped address.  Otherwise set configMTIME_BASE_ADDRESS to 0.  See www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html"
#endif

#ifndef configMTIMECMP_BASE_ADDRESS
    #warning "configMTIMECMP_BASE_ADDRESS must be defined in FreeRTOSConfig.h. If the target chip includes a memory-mapped mtimecmp register then set configMTIMECMP_BASE_ADDRESS to the mapped address.  Otherwise set configMTIMECMP_BASE_ADDRESS to 0.  See www.FreeRTOS.org/Using-FreeRTOS-on-RISC-V.html"
#endif

/* Let the user override the pre-loading of the initial RA. */
#ifdef configTASK_RETURN_ADDRESS
    #define portTASK_RETURN_ADDRESS    configTASK_RETURN_ADDRESS
#else
    #define portTASK_RETURN_ADDRESS    0
#endif

/* The stack used by interrupt service routines.  Set configISR_STACK_SIZE_WORDS
 * to use a statically allocated array as the interrupt stack.  Alternative leave
 * configISR_STACK_SIZE_WORDS undefined and update the linker script so that a
 * linker variable names __freertos_irq_stack_top has the same value as the top
 * of the stack used by main.  Using the linker script method will repurpose the
 * stack that was used by main before the scheduler was started for use as the
 * interrupt stack after the scheduler has started. */
#ifdef configISR_STACK_SIZE_WORDS
static __attribute__( ( aligned( 16 ) ) ) StackType_t xISRStack[ configISR_STACK_SIZE_WORDS ] = { 0 };
const StackType_t xISRStackTop = ( StackType_t ) &( xISRStack[ configISR_STACK_SIZE_WORDS & ~portBYTE_ALIGNMENT_MASK ] );

/* Don't use 0xa5 as the stack fill bytes as that is used by the kernel for
 * the task stacks, and so will legitimately appear in many positions within
 * the ISR stack. */
    #define portISR_STACK_FILL_BYTE    0xee
#else
    //extern const uint32_t __freertos_irq_stack_top[];
    //const StackType_t xISRStackTop = (StackType_t) __freertos_irq_stack_top;
    extern const uint32_t __stack_top__[];
    const StackType_t xISRStackTop = (StackType_t)__stack_top__;
#endif

/*
 * Setup the timer to generate the tick interrupts.  The implementation in this
 * file is weak to allow application writers to change the timer used to
 * generate the tick interrupt.
 */
void vPortSetupTimerInterrupt( void ) __attribute__( ( weak ) );

/*-----------------------------------------------------------*/

/* Used to program the machine timer compare register. */
/* \brief 下次发生中断的硬件定时器计数值 */
uint64_t ullNextTime = 0ULL;
const uint64_t * pullNextTime = &ullNextTime;
/* \brief 一个 tick 对应的定时器计数增量，此处假设计数器增量不会超过 32 位数据的表示范围（即不会发生数值溢出）*/
const size_t uxTimerIncrementsForOneTick = (size_t)((configCPU_CLOCK_HZ) / (configTICK_RATE_HZ));
/* \brief 机器对比定时器基地址 */
uint32_t const ullMachineTimerCompareRegisterBase = configMTIMECMP_BASE_ADDRESS;
volatile uint64_t * pullMachineTimerCompareRegister = NULL;

/* Holds the critical nesting value - deliberately non-zero at start up to
 * ensure interrupts are not accidentally enabled before the scheduler starts. */
size_t xCriticalNesting = ( size_t ) 0xaaaaaaaa;
size_t * pxCriticalNesting = &xCriticalNesting;

/* Used to catch tasks that attempt to return from their implementing function. */
size_t xTaskReturnAddress = ( size_t ) portTASK_RETURN_ADDRESS;

/* Set configCHECK_FOR_STACK_OVERFLOW to 3 to add ISR stack checking to task
 * stack checking.  A problem in the ISR stack will trigger an assert, not call
 * the stack overflow hook function (because the stack overflow hook is specific
 * to a task stack, not the ISR stack). */
#if defined( configISR_STACK_SIZE_WORDS ) && ( configCHECK_FOR_STACK_OVERFLOW > 2 )
    #warning "This path not tested, or even compiled yet."

    static const uint8_t ucExpectedStackBytes[] =
    {
        portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
        portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
        portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
        portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
        portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE
    }; \

    #define portCHECK_ISR_STACK()    configASSERT( ( memcmp( ( void * ) xISRStack, ( void * ) ucExpectedStackBytes, sizeof( ucExpectedStackBytes ) ) == 0 ) )
#else /* if defined( configISR_STACK_SIZE_WORDS ) && ( configCHECK_FOR_STACK_OVERFLOW > 2 ) */
    /* Define the function away. */
    #define portCHECK_ISR_STACK()
#endif /* configCHECK_FOR_STACK_OVERFLOW > 2 */

#if (configMTIME_BASE_ADDRESS != 0) && (configMTIMECMP_BASE_ADDRESS != 0)
/**
 * \brief 平台定时器中断初始化
 */
void vPortSetupTimerInterrupt(void){
    uint32_t                  ulCurrentTimeHigh, ulCurrentTimeLow;
    /* 8 字节数据中高 32 位即第 4 字节处 */
    volatile uint32_t * const pulTimeHigh = (volatile uint32_t * const)((configMTIME_BASE_ADDRESS) + 4UL);
    volatile uint32_t * const pulTimeLow  = (volatile uint32_t * const)(configMTIME_BASE_ADDRESS);
    volatile uint32_t         ulHartId;

    /* 获取当前的核 ID */
    __asm volatile("csrr %0, mhartid" : "=r" (ulHartId));
    /* 获取对应的硬件比对寄存器 */
    pullMachineTimerCompareRegister = (volatile uint64_t *)(ullMachineTimerCompareRegisterBase + (ulHartId * sizeof(uint64_t)));

    /* 在 32 位系统中读取 64 位计时器需分两次操作，可能因中断导致数据不一致，
     * 即第一次读到的 High 值是旧值，第二次读到的 Low 值是新值，组合后的 64 位数据是错位的错误值，
     * 例如：64 位计时器原始值为 0x00000001_FFFFFFFF，即将溢出到 0x00000002_00000000，
     * 所以需要通过循环校验高 32 位是否变化，确保读取的 64 位时间戳完整有效 */
    do {
        ulCurrentTimeHigh = *pulTimeHigh;
        ulCurrentTimeLow  = *pulTimeLow;
    } while (ulCurrentTimeHigh != *pulTimeHigh);

    ullNextTime = (uint64_t)ulCurrentTimeHigh;
    /* 高 4 字节字向上 32 位对齐 */
    ullNextTime <<= 32ULL;
    ullNextTime |= (uint64_t)ulCurrentTimeLow;
    ullNextTime += (uint64_t)uxTimerIncrementsForOneTick;

    /* 设置下次触发中断的计数值 */
    *pullMachineTimerCompareRegister = ullNextTime;

    /* 为下一个 tick 中断后的时间计数使用做准备 */
    ullNextTime += (uint64_t)uxTimerIncrementsForOneTick;
}

#endif /* (configMTIME_BASE_ADDRESS != 0) && (configMTIME_BASE_ADDRESS != 0) */

/**
 * \brief 平台调度启动函数
 *
 * @return 成功则不返回
 */
BaseType_t xPortStartScheduler(void){
    extern void xPortStartFirstTask(void);

    #if (configASSERT_DEFINED == 1)
    {
        /* 检查中断栈的对齐情况，该栈与调度器启动前 main 函数使用的栈是同一个栈。*/
        configASSERT((xISRStackTop & portBYTE_ALIGNMENT_MASK) == 0);

        #ifdef configISR_STACK_SIZE_WORDS
        {
            memset((void *)xISRStack, portISR_STACK_FILL_BYTE, sizeof(xISRStack));
        }
        #endif /* configISR_STACK_SIZE_WORDS */
    }
    #endif /* configASSERT_DEFINED */

    /* 若存在核心本地中断控制器（CLINT），则可直接使用本文件中的默认实现；
     * 否则必须实现 vPortSetupTimerInterrupt() 函数
     * 来配置用于生成节拍中断（tick interrupt）的时钟源 */
    vPortSetupTimerInterrupt();

    #if ((configMTIME_BASE_ADDRESS != 0) && (configMTIMECMP_BASE_ADDRESS != 0))
    {
        /* 启用 mtime 定时器和外部中断：1 << 7 对应定时器中断使能位。
         * 1 << 11 对应外部中断使能位。*/
        __asm volatile("csrs mie, %0" ::"r" (0x880));
    }
    #endif /* (configMTIME_BASE_ADDRESS != 0) && (configMTIMECMP_BASE_ADDRESS != 0) */

    /* 启动第一个任务 */
    xPortStartFirstTask();

    /* 正常情况下不应执行到此，因为调用 xPortStartFirstTask() 后，
     * 系统中应该只有任务（task）在运行。*/
    return pdFAIL;
}
/*-----------------------------------------------------------*/

void vPortEndScheduler( void )
{
    /* Not implemented. */
    for( ; ; )
    {
    }
}
/*-----------------------------------------------------------*/

/**
 * \brief configSUPPORT_STATIC_ALLOCATION 已设置为 1，
 *        因此应用程序必须实现 vApplicationGetIdleTaskMemory() 函数，以提供空闲任务所需的内存。
 *
 * @param ppxIdleTaskTCBBuffer   返回空闲任务的 TCB 缓存
 * @param ppxIdleTaskStackBuffer 返回将用作空闲任务栈的数组指针
 * @param pulIdleTaskStackSize   返回空闲任务栈数组的大小（以字为单位）
 */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
                                   StackType_t  **ppxIdleTaskStackBuffer,
                                   uint32_t      *pulIdleTaskStackSize){
    /* 若需在本函数内声明提供给空闲任务的缓冲区，
     * 则必须将其声明为 static 类型——否则缓冲区将被分配在栈上，
     * 导致函数退出后内存失效 */
    static StaticTask_t xIdleTaskTCB;
    static StackType_t  uxIdleTaskStack[configMINIMAL_STACK_SIZE];

    /* 返回一个指向 StaticTask_t 结构的指针，该结构将用于存储空闲任务的状态数据（TCB） */
    *ppxIdleTaskTCBBuffer = &xIdleTaskTCB;

    /* 返回将用作空闲任务栈的数组指针 */
    *ppxIdleTaskStackBuffer = uxIdleTaskStack;

    /* 返回 *ppxIdleTaskStackBuffer 所指数组的大小（以字为单位）。
       需注意：由于该数组必须为 StackType_t 类型，因此 configMINIMAL_STACK_SIZE 的
       单位是字而非字节 */
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

/**
 * \brief 当 configSUPPORT_STATIC_ALLOCATION 和 configUSE_TIMERS 均配置为 1 时，
 *        应用程序必须实现 vApplicationGetTimerTaskMemory() 函数，
 *        以提供定时器服务任务（Timer Service Task）所需的内存空间
 *
 * @param ppxTimerTaskTCBBuffer   返回定时器任务的 TCB 缓存
 * @param ppxTimerTaskStackBuffer 返回将用作定时器任务栈的数组指针
 * @param pulTimerTaskStackSize   返回定时器任务栈数组的大小（以字为单位）
 */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
                                    StackType_t  **ppxTimerTaskStackBuffer,
                                    uint32_t      *pulTimerTaskStackSize){
    /* 如果提供给定时器任务（Timer Task）的内存缓冲区需要在此函数内声明，
     * 则必须将其声明为 static 类型，否则这些缓冲区会被分配在栈空间上，导致函数退出后内存失效 */
    static StaticTask_t xTimerTaskTCB;
    static StackType_t  uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];

    /* 此处返回一个指向 StaticTask_t 结构体的指针，该结构体将用于存储定时器任务（Timer Task）的 TCB 数据 */
    *ppxTimerTaskTCBBuffer = &xTimerTaskTCB;

    /* 此处返回将用作定时器任务（Timer Task）栈空间的数组指针 */
    *ppxTimerTaskStackBuffer = uxTimerTaskStack;

    /* 此处返回由 *ppxTimerTaskStackBuffer 所指向的栈数组大小。需特别注意：
       由于该数组类型为 StackType_t（栈单元类型），因此 configTIMER_TASK_STACK_DEPTH
       的数值单位是字，而非字节 */
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
